package org.acghub.mtdb.core;

import com.alibaba.fastjson2.JSONObject;
import org.acghub.mtdb.common.exception.MarshalException;
import org.bson.Document;

import java.util.Objects;
import java.util.StringJoiner;

/**
 * @author zhouhe
 */
public final class TRecord<K, V> extends TBean {

    private static final String RECORD_VARNAME = "value";
    private final TTable<K, V> table;
    private final Lockey lockey;
    private V value;
    private State state;

    private volatile long lastAccessTime = System.nanoTime();
    private Object snapshotKey = null;
    private Object snapshotValue = null;
    private State snapshotState = null;

    TRecord(TTable<K, V> table, V value, Lockey lockey, State state) {
        super(null, RECORD_VARNAME);
        this.table = table;
        if (null != value) {
            Logs.link(value, this, RECORD_VARNAME, State.INDB_GET != state);
        }
        this.value = value;
        this.lockey = lockey;
        this.state = state;
    }

    void access() {
        lastAccessTime = System.nanoTime();
    }

    long getLastAccessTime() {
        return lastAccessTime;
    }

    @Override
    protected void onConstructor() {

    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        TRecord<?, ?> tRecord = (TRecord<?, ?>) o;
        return lastAccessTime == tRecord.lastAccessTime && table.equals(tRecord.table) && lockey.equals(tRecord.lockey) && value.equals(tRecord.value) && state == tRecord.state && snapshotKey.equals(tRecord.snapshotKey) && snapshotValue.equals(tRecord.snapshotValue) && snapshotState == tRecord.snapshotState;
    }

    @Override
    public int hashCode() {
        return Objects.hash(table, lockey, value, state, lastAccessTime, snapshotKey, snapshotValue, snapshotState);
    }

    @Override
    public String toString() {
        return new StringJoiner(", ", TRecord.class.getSimpleName() + "[", "]")
                .add("table=" + table)
                .add("lockey=" + lockey)
                .add("value=" + value)
                .add("state=" + state)
                .add("lastAccessTime=" + lastAccessTime)
                .add("snapshotKey=" + snapshotKey)
                .add("snapshotValue=" + snapshotValue)
                .add("snapshotState=" + snapshotState)
                .toString();
    }

    @Override
    public Document marshal(Document writer) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void unmarshal(Document reader) throws MarshalException {
        throw new UnsupportedOperationException();
    }

    @Override
    public JSONObject marshal(JSONObject writer) {
        throw new UnsupportedOperationException();
    }

    @Override
    public void unmarshal(JSONObject reader) throws MarshalException {
        throw new UnsupportedOperationException();
    }

    public State state() {
        return state;
    }

    public K getKey() {
        return (K) lockey.getKey();
    }

    public V getValue() {
        return value;
    }

    Lockey getLockey() {
        return lockey;
    }

    LogKey getLogKey() {
        return new LogKey(this, RECORD_VARNAME);
    }

    void clear() {
        snapshotKey = null;
        snapshotValue = null;
        snapshotState = null;
    }

    boolean exist() {
        if (Trace.isDebugEnabled()) {
            Trace.debug("TRecord.exist " + this);
        }
        switch (snapshotState) {
            case INDB_GET:
            case INDB_ADD:
            case ADD:
                return true;
            case INDB_REMOVE:
            case REMOVE:
        }
        return false;
    }

    Object find() {
        if (Trace.isDebugEnabled()) {
            Trace.debug("TRecord.find " + this);
        }
        switch (snapshotState) {
            case INDB_GET:
            case INDB_ADD:
            case ADD:
                return snapshotValue;
            case INDB_REMOVE:
            case REMOVE:
        }
        return null;
    }

    private void remove0() {
        Logs.link(value, null, null);
        Transaction.currentSavepoint().addIfAbsent(getLogKey(), new LogAddRemove());
        value = null;
    }

    boolean remove() {
        switch (state) {
            case INDB_GET, INDB_ADD -> {
                remove0();
                state = State.INDB_REMOVE;
                return true;
            }
            case ADD -> {
                remove0();
                state = State.REMOVE;
                return true;
            }
            default -> {
                return false;
            }
        }
    }

    private void add0(V value) {
        Logs.link(value, this, RECORD_VARNAME);
        Transaction.currentSavepoint().addIfAbsent(getLogKey(), new LogAddRemove());
        this.value = value;
    }

    boolean add(V value) {
        switch (state) {
            case INDB_REMOVE -> {
                add0(value);
                state = State.INDB_ADD;
                return true;
            }
            case REMOVE -> {
                add0(value);
                state = State.ADD;
                return true;
            }
            default -> {
                return false;
            }
        }
    }

    public enum State {
        INDB_GET(1, "查询装载进入"),

        INDB_ADD(2, "新增记录，在数据库中存在该记录。这种情况原因：删除操作还没有flush，又执行了增加。"),

        INDB_REMOVE(3, "删除，在数据库中存在该记录。"),

        ADD(4, "新增的记录"),

        REMOVE(5, "ADD操作还没有flush，又执行了删除。此状态记录只存在于事务过程中，事务结束就会从cache中删除。"),

        ;

        private final int value;
        private final String desc;

        State(int value, String desc) {
            this.value = value;
            this.desc = desc;
        }

        public int getValue() {
            return value;
        }

        public String getDesc() {
            return desc;
        }
    }


    private class LogAddRemove implements Log {
        private final V saved_value;
        private final State saved_state;

        LogAddRemove() {
            saved_value = value;
            saved_state = state;
        }

        @Override
        public void commit() {
            table.onChanged(TRecord.this, false, saved_state);
        }

        @Override
        public void rollback() {
            value = saved_value;
            state = saved_state;
        }

        @Override
        public String toString() {
            return "state=" + saved_state + " value=" + saved_value;
        }
    }
    @Override
    public void notify(LogNotify notify) {
        table.onChanged(this, notify);
    }
}
